import type { FormatOptions as DateFnsFormatOptions } from "date-fns";

import type { DateLibOptions } from "../../classes/DateLib.js";
import { getMonthCode } from "../utils/calendarMath.js";
import { toHebrewDate } from "../utils/dateConversion.js";
import { hebrewMonthNumber } from "../utils/serial.js";

const fallbackMonthNames: Record<string, { en: string; he: string }> = {
  tishrei: { en: "Tishrei", he: "תשרי" },
  cheshvan: { en: "Cheshvan", he: "חשוון" },
  kislev: { en: "Kislev", he: "כסלו" },
  tevet: { en: "Tevet", he: "טבת" },
  shevat: { en: "Shevat", he: "שבט" },
  adarI: { en: "Adar I", he: "אדר א׳" },
  adar: { en: "Adar", he: "אדר" },
  nisan: { en: "Nisan", he: "ניסן" },
  iyar: { en: "Iyar", he: "אייר" },
  sivan: { en: "Sivan", he: "סיוון" },
  tamuz: { en: "Tammuz", he: "תמוז" },
  av: { en: "Av", he: "אב" },
  elul: { en: "Elul", he: "אלול" },
};

const fallbackWeekdayNames = {
  long: {
    en: [
      "Sunday",
      "Monday",
      "Tuesday",
      "Wednesday",
      "Thursday",
      "Friday",
      "Saturday",
    ],
    he: [
      "יום ראשון",
      "יום שני",
      "יום שלישי",
      "יום רביעי",
      "יום חמישי",
      "יום שישי",
      "שבת",
    ],
  },
  narrow: {
    en: ["S", "M", "T", "W", "T", "F", "S"],
    he: ["א", "ב", "ג", "ד", "ה", "ו", "ש"],
  },
};

const getLocaleCode = (options?: DateLibOptions): string => {
  return options?.locale?.code ?? "he";
};

const getMonthCodeForDate = (date: Date) => {
  const hebrew = toHebrewDate(date);
  return getMonthCode(hebrew.year, hebrew.monthIndex);
};

const formatMonthName = (date: Date, localeCode: string): string => {
  try {
    return new Intl.DateTimeFormat(localeCode, {
      month: "long",
      calendar: "hebrew",
    }).format(date);
  } catch {
    const code = getMonthCodeForDate(date);
    const isHebrew = localeCode.startsWith("he");
    return isHebrew ? fallbackMonthNames[code].he : fallbackMonthNames[code].en;
  }
};

const formatWeekdayName = (
  date: Date,
  localeCode: string,
  width: "long" | "narrow",
): string => {
  try {
    return new Intl.DateTimeFormat(localeCode, {
      weekday: width,
      calendar: "hebrew",
    }).format(date);
  } catch {
    const index = date.getDay();
    const isHebrew = localeCode.startsWith("he");
    return isHebrew
      ? fallbackWeekdayNames[width].he[index]
      : fallbackWeekdayNames[width].en[index];
  }
};

const formatDateStyle = (
  date: Date,
  localeCode: string,
  style: "long" | "full",
): string => {
  try {
    return new Intl.DateTimeFormat(localeCode, {
      dateStyle: style,
      calendar: "hebrew",
    }).format(date);
  } catch {
    const hebrew = toHebrewDate(date);
    const month = formatMonthName(date, localeCode);
    if (style === "full") {
      const weekday = formatWeekdayName(date, localeCode, "long");
      return `${weekday}, ${month} ${hebrew.day}, ${hebrew.year}`;
    }
    return `${month} ${hebrew.day}, ${hebrew.year}`;
  }
};

const formatNumber = (value: number): string => {
  return value.toString();
};

const buildTimeFormat = (
  date: Date,
  localeCode: string,
  formatStr: string,
): string => {
  const hour12 = formatStr.includes("a");
  return new Intl.DateTimeFormat(localeCode, {
    hour: "numeric",
    minute: "numeric",
    hour12,
  }).format(date);
};

/** Hebrew calendar formatting override. */
export function format(
  date: Date,
  formatStr: string,
  options?: DateFnsFormatOptions,
): string {
  const extendedOptions = options as DateLibOptions | undefined;
  const localeCode = getLocaleCode(extendedOptions);
  const hebrew = toHebrewDate(date);
  const monthNumber = hebrewMonthNumber(hebrew.monthIndex);

  switch (formatStr) {
    case "LLLL y":
    case "LLLL yyyy":
      return `${formatMonthName(date, localeCode)} ${formatNumber(hebrew.year)}`;
    case "LLLL":
      return formatMonthName(date, localeCode);
    case "PPP":
      return formatDateStyle(date, localeCode, "long");
    case "PPPP":
      return formatDateStyle(date, localeCode, "full");
    case "cccc":
      return formatWeekdayName(date, localeCode, "long");
    case "cccccc":
      return formatWeekdayName(date, localeCode, "narrow");
    case "yyyy":
    case "y":
      return formatNumber(hebrew.year);
    case "yyyy-MM":
      return `${formatNumber(hebrew.year)}-${formatNumber(monthNumber).padStart(2, "0")}`;
    case "yyyy-MM-dd":
      return `${formatNumber(hebrew.year)}-${formatNumber(monthNumber).padStart(2, "0")}-${formatNumber(hebrew.day).padStart(2, "0")}`;
    case "MM":
      return formatNumber(monthNumber).padStart(2, "0");
    case "M":
      return formatNumber(monthNumber);
    case "dd":
      return formatNumber(hebrew.day).padStart(2, "0");
    case "d":
      return formatNumber(hebrew.day);
    default:
      if (/[Hh]/.test(formatStr) && /m/.test(formatStr)) {
        return buildTimeFormat(date, localeCode, formatStr);
      }
      return `${formatNumber(hebrew.day)}/${formatNumber(monthNumber)}/${formatNumber(hebrew.year)}`;
  }
}
